package com.hospital.register.order.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.github.wxpay.sdk.WXPayConstants;
import com.hospital.register.common.exception.BusinessException;
import com.hospital.register.common.helper.HttpRequestHelper;
import com.hospital.register.common.rabbit.constant.MqConst;
import com.hospital.register.common.rabbit.service.RabbitService;
import com.hospital.register.common.result.Result;
import com.hospital.register.common.result.ResultCodeEnum;
import com.hospital.register.constants.HospitalSetConstants;
import com.hospital.register.constants.PatientConstants;
import com.hospital.register.core.client.HospitalFeignClient;
import com.hospital.register.dto.order.OrderDayDto;
import com.hospital.register.dto.order.OrderPageDto;
import com.hospital.register.dto.order.OrderRetransmissionDto;
import com.hospital.register.dto.order.OrderSubmissionDto;
import com.hospital.register.enums.OrderStatusEnum;
import com.hospital.register.enums.PaymentTypeEnum;
import com.hospital.register.enums.RefundStatusEnum;
import com.hospital.register.model.order.RefundInfo;
import com.hospital.register.order.mapper.OrderInfoMapper;
import com.hospital.register.order.service.OrderInfoService;
import com.hospital.register.order.service.RefundInfoService;
import com.hospital.register.order.service.WxpayService;
import com.hospital.register.user.client.PatientFeignClient;
import com.hospital.register.vo.core.api.HospitalSignVo;
import com.hospital.register.vo.core.api.ScheduleDetailVo;
import com.hospital.register.vo.order.*;
import com.hospital.register.vo.user.PatientQueryVo;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.concurrent.ThreadLocalRandom;

@Service
public class OrderInfoServiceImpl implements OrderInfoService {

    /**
     * 访问订单数据
     */
    @Autowired
    private OrderInfoMapper orderInfoMapper;

    /**
     * 访问医院信息
     */
    @Autowired
    private HospitalFeignClient hospitalFeignClient;

    /**
     * 访问就诊人信息
     */
    @Autowired
    private PatientFeignClient patientFeignClient;

    /**
     * Rabbit 工具类
     */
    @Autowired
    private RabbitService rabbitService;

    /**
     * 处理退款相关业务
     */
    @Autowired
    private RefundInfoService refundInfoService;

    /**
     * 处理微信支付相关业务
     */
    @Autowired
    private WxpayService wxpayService;

    /**
     * 记录日志
     */
    private final Logger logger = LoggerFactory.getLogger(OrderInfoServiceImpl.class);

    @Override
    @Transactional
    public Long submit(OrderSubmissionDto submission) {
        // 查询订单就诊人、所属科室、医院签名
        PatientQueryVo patient = this.patientFeignClient.getPatientById(submission.getPatientId(),
                submission.getUserId());
        if (patient == null) {
            throw new BusinessException("查询就诊人失败", ResultCodeEnum.PARAM_ERROR.getCode());
        }

        if (patient.getStatus() != PatientConstants.STATUS_AUTHENTICATED) {
            // throw new BusinessException("就诊人尚未认证，无法下单",
            // ResultCodeEnum.PARAM_ERROR.getCode());
            // 好像没有设计就诊人认证的功能，忘记了，我自己也测试不了了。
        }

        ScheduleDetailVo schedule = this.hospitalFeignClient.getScheduleById(submission.getScheduleId());
        if (schedule == null) {
            throw new BusinessException("查询排班信息失败", ResultCodeEnum.PARAM_ERROR.getCode());
        }

        HospitalSignVo sign = this.hospitalFeignClient.getSign(schedule.getHoscode());
        if (sign == null) {
            throw new BusinessException("医院信息查询失败", ResultCodeEnum.DATA_ERROR.getCode());
        }

        if (!HospitalSetConstants.STATUS_ENABLED.equals(sign.getStatus())) {
            throw new BusinessException("医院已锁定", ResultCodeEnum.DATA_ERROR.getCode());
        }

        long currentTime = System.currentTimeMillis();
        if (currentTime < schedule.getStartTime().getTime()// 当前时间<开始预约时间
                || currentTime > schedule.getEndTime().getTime()) {// 当前时间>预约结束时间
            throw new BusinessException(ResultCodeEnum.TIME_NO);
        }

        // 本地新增订单
        OrderMessageVo initialOrder = this.insert(patient, schedule);
        // 调用医院接口，实现预约挂号操作
        this.retransmissionOrder(sign, initialOrder, patient, schedule);
        // 发送mq消息，号源更新和短信通知
        // 由医院模块的监听器接收
        rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_ORDER, MqConst.ROUTING_ORDER, initialOrder);
        return initialOrder.getId();
    }

    @Override
    public OrderQueryVo selectById(Long id) {
        OrderQueryVo order = this.orderInfoMapper.selectById(id);
        order.setParam(new OrderQueryVo.OtherInfo());
        String statusName = OrderStatusEnum.getStatusNameByStatus(order.getOrderStatus());
        order.getParam().setOrderStatusString(statusName);
        return order;
    }

    @Override
    public Page<OrderQueryVo> selectPage(Page<OrderQueryVo> page, OrderPageDto conditions) {
        return this.orderInfoMapper.selectPage(page, conditions);
    }

    @Override
    public boolean updateStatusById(Long id, Byte oldOrderStatus, Byte newOrderStatus, Date updateTime) {
        return this.orderInfoMapper.updateStatusById(id, oldOrderStatus, newOrderStatus, updateTime) == 1;
    }

    @Override
    @Transactional
    public RefundInfo beginCancel(Long userId, Long orderId, Byte paymentType) {
        // 根据 id 查询订单
        OrderQueryVo order = this.selectById(orderId);
        if (order == null) {
            throw new BusinessException("订单不存在", ResultCodeEnum.PARAM_ERROR.getCode());
        }

        // 如果传入的用户 id 与 订单的用户 id 不一致，则不允许取消
        if (!userId.equals(order.getUserId())) {
            throw new BusinessException("目标用户没有此订单", ResultCodeEnum.PARAM_ERROR.getCode());
        }

        // 如果当前时间>订单的退号时间，则不允许取消
        if (System.currentTimeMillis() > order.getQuitTime().getTime()) {
            throw new BusinessException("已超过退号时间，不能取消", ResultCodeEnum.CANCEL_ORDER_FAIL.getCode());
        }

        // 下面无法使用 switch 语句，因为编译器识别不出来这个枚举常量
        if (OrderStatusEnum.UNPAID.getStatus().equals(order.getOrderStatus())) {
            return null;
        } else if (OrderStatusEnum.PAID.getStatus().equals(order.getOrderStatus())) {
            // 新增微信支付的退款记录
            return this.refundInfoService.generateIfAbsent(orderId, paymentType);
        } else if (OrderStatusEnum.GET_NUMBER.getStatus().equals(order.getOrderStatus())) {
            throw new BusinessException("订单已取号，不可重复操作", ResultCodeEnum.PARAM_ERROR.getCode());
        } else if (OrderStatusEnum.CANCEL.getStatus().equals(order.getOrderStatus())) {
            throw new BusinessException("订单已取消，不可重复操作", ResultCodeEnum.PARAM_ERROR.getCode());
        } else {
            throw new BusinessException("订单数据异常，请联系管理员", ResultCodeEnum.DATA_ERROR.getCode());
        }
    }

    @Override
    @Transactional
    public void finishCancel(Long orderId, RefundInfo refundInfo) {
        Date currentTime = new Date();

        // 根据 id 查询订单
        OrderQueryVo order = this.selectById(orderId);
        if (order == null) {
            if (refundInfo != null) {
                this.refundInfoService.deleteById(refundInfo.getId(), currentTime);
            }
            throw new BusinessException("订单不存在或服务异常", ResultCodeEnum.PARAM_ERROR.getCode());
        }

        // 已支付但未创建退款记录，抛异常
        if (OrderStatusEnum.PAID.getStatus().equals(order.getOrderStatus())
                && refundInfo == null) {
            throw new BusinessException("完成退款时，退款信息为空", ResultCodeEnum.DATA_ERROR.getCode());
        }

        // 获取医院签名
        HospitalSignVo sign = this.hospitalFeignClient.getSign(order.getHoscode());
        if (sign == null) {
            this.refundInfoService.deleteById(refundInfo.getId(), currentTime);
            throw new BusinessException("医院信息查询失败", ResultCodeEnum.DATA_ERROR.getCode());
        }

        // 请求院方退号
        Result<Object> hospitalCancelled = this.hospitalCancelOrder(order, sign);
        if (hospitalCancelled.getCode() != 200) {
            this.refundInfoService.deleteById(refundInfo.getId(), currentTime);
            throw new BusinessException("院方拒绝取消订单或服务异常，" + hospitalCancelled.getMessage(),
                    ResultCodeEnum.CANCEL_ORDER_FAIL.getCode());
        }

        if (OrderStatusEnum.UNPAID.getStatus().equals(order.getOrderStatus())) {
            this.orderInfoMapper.updateStatusById(order.getId(),
                    OrderStatusEnum.UNPAID.getStatus(),
                    OrderStatusEnum.CANCEL.getStatus(), new Date());

        } else if (OrderStatusEnum.PAID.getStatus().equals(order.getOrderStatus())
                && PaymentTypeEnum.WEIXIN.getStatus().equals(refundInfo.getPaymentType())) {
            // 微信退款，由于微信支付的证书有问题，所以测试不了了，详见测试类
            // 向微信发起退款请求
            Map<String, String> refundResult = this.wxpayService.refund(refundInfo);

            // 空响应，抛出异常
            if (refundResult == null || refundResult.isEmpty()) {
                this.refundInfoService.deleteById(refundInfo.getId(), currentTime);
                throw new BusinessException("微信退款服务异常，空响应", ResultCodeEnum.CANCEL_ORDER_FAIL.getCode());
            }

            // 退款失败，也抛出异常
            if (!WXPayConstants.SUCCESS.equalsIgnoreCase(refundResult.get("result_code"))) {
                this.refundInfoService.deleteById(refundInfo.getId(), currentTime);
                throw new BusinessException("微信退款失败", ResultCodeEnum.CANCEL_ORDER_FAIL.getCode());
            }

            // 更新退款状态
            RefundInfo newRefundInfo = new RefundInfo();
            newRefundInfo.setCallbackTime(currentTime);
            refundInfo.setTradeNo(refundResult.get("refund_id"));
            refundInfo.setRefundStatus(RefundStatusEnum.REFUND.getStatus());
            refundInfo.setCallbackContent(JSONObject.toJSONString(refundResult));
            this.refundInfoService.updateById(newRefundInfo);

            // 通过 mq 通知系统模块，更新排班的浩源。由于无法测试，就不实现了。
            // 更新订单状态
            boolean cancelled = this.orderInfoMapper.updateStatusById(refundInfo.getOrderId(),
                    OrderStatusEnum.PAID.getStatus(),
                    OrderStatusEnum.CANCEL.getStatus(), currentTime) == 1;
            if (!cancelled) {
                throw new BusinessException("更新订单状态失败", ResultCodeEnum.CANCEL_ORDER_FAIL.getCode());
            }
        }

    }

    @Override
    public OrderDaysVo countEveryDayOrder(OrderDayDto conditions) {
        OrderDaysVo result = new OrderDaysVo();

        // 查询数据库，重新封装，返回
        List<OrderDayVo> daysNumber = this.orderInfoMapper.countEveryDayOrder(conditions);
        if (daysNumber == null || daysNumber.isEmpty()) {
            return result;
        }

        result.setDateList(new ArrayList<>());
        result.setCountList(new ArrayList<>());

        for (OrderDayVo dayNumber : daysNumber) {
            result.getDateList().add(dayNumber.getReserveDate());
            result.getCountList().add(dayNumber.getCount());
        }

        return result;
    }

    /**
     * 通知医院取消订单
     *
     * @param order 订单信息
     * @param sign  医院签名
     */
    private Result<Object> hospitalCancelOrder(OrderQueryVo order, HospitalSignVo sign) {
        String url = sign.getApiUrl() + "/order/updateCancelStatus";
        Map<String, Object> paramMap = new HashMap<>();

        paramMap.put("hoscode", order.getHoscode());
        paramMap.put("hosRecordId", order.getHosRecordId());

        return HttpRequestHelper.sendToHospital(paramMap, url, sign.getSignKey(), Object.class);
    }

    /**
     * 新增订单
     *
     * @param patient  患者信息
     * @param schedule 排班信息
     * @return 新增的订单信息
     */
    private OrderMessageVo insert(PatientQueryVo patient, ScheduleDetailVo schedule) {
        // 属性拷贝，新增
        OrderMessageVo order = new OrderMessageVo();
        BeanUtils.copyProperties(schedule, order);

        order.setOutTradeNo(System.currentTimeMillis() + "-" + ThreadLocalRandom.current().nextInt(100));
        order.setOrderStatus(OrderStatusEnum.UNPAID.getStatus());// 待支付
        order.setScheduleId(schedule.getId());
        order.setUserId(patient.getUserId());
        order.setPatientId(patient.getId());
        order.setPatientName(patient.getName());
        order.setPatientPhone(patient.getPhone());

        this.orderInfoMapper.insert(order);
        return order;
    }

    /**
     * 向医院提交挂号订单
     *
     * @param sign         医院的签名信息
     * @param initialOrder 挂号订单
     * @param patient      患者信息
     * @param schedule     科室信息
     * @return 院方完整的挂号订单
     */
    private OrderMessageVo retransmissionOrder(HospitalSignVo sign, OrderMessageVo initialOrder, PatientQueryVo patient,
            ScheduleDetailVo schedule) {
        // 封装参数，发起请求，合并请求结果到订单实体中，一堆的属性拷贝
        OrderRetransmissionDto transmissionInfo = new OrderRetransmissionDto();
        transmissionInfo.setId(initialOrder.getId());
        transmissionInfo.setHoscode(initialOrder.getHoscode());
        transmissionInfo.setDepcode(initialOrder.getDepcode());
        transmissionInfo.setHosScheduleId(schedule.getHosScheduleId());
        transmissionInfo.setReserveDate(new DateTime(initialOrder.getReserveDate()).toString("yyyy-MM-dd"));
        transmissionInfo.setReserveTime(initialOrder.getReserveTime());
        transmissionInfo.setAmount(initialOrder.getAmount());
        transmissionInfo.setName(patient.getName());
        transmissionInfo.setCertificatesType(patient.getCertificatesType());
        transmissionInfo.setCertificatesNo(patient.getCertificatesNo());
        transmissionInfo.setSex(patient.getSex());
        transmissionInfo.setBirthdate(patient.getBirthdate());
        transmissionInfo.setPhone(patient.getPhone());
        transmissionInfo.setIsMarry(patient.getIsMarry());
        transmissionInfo.setProvinceCode(patient.getProvinceCode());
        transmissionInfo.setCityCode(patient.getCityCode());
        transmissionInfo.setDistrictCode(patient.getDistrictCode());
        transmissionInfo.setAddress(patient.getAddress());
        transmissionInfo.setContactsName(patient.getContactsName());
        transmissionInfo.setContactsCertificatesType(patient.getContactsCertificatesType());
        transmissionInfo.setContactsCertificatesNo(patient.getContactsCertificatesNo());
        transmissionInfo.setContactsPhone(patient.getContactsPhone());
        transmissionInfo.setTimestamp(HttpRequestHelper.getTimestamp());

        // 将所有内容转为 TreeMap 集合，生成签名
        TreeMap<String, Object> paramMap = this.orderToTreeMap(transmissionInfo);
        String requestSign = HttpRequestHelper.getSign(paramMap, sign.getSignKey());

        transmissionInfo.setSign(requestSign);
        paramMap.put("sign", requestSign);

        // 请求医院系统接口
        JSONObject result = HttpRequestHelper.sendRequest(paramMap, sign.getApiUrl() + "/order/submitOrder");
        Integer responseCode = result.getInteger("code");
        if (responseCode == null || responseCode != 200) {
            this.logger.error("向医院提交订单失败，响应结果：" + result.toJSONString());
            throw new BusinessException("向医院提交订单失败", ResultCodeEnum.FAIL.getCode());
        }

        // 更新订单
        OrderConfirmedVo confirmedOrder = result.getJSONObject("data").toJavaObject(OrderConfirmedVo.class);

        String chineseReserveTime = (initialOrder.getReserveTime() == 0 ? "上午" : "下午");
        initialOrder.setChineseReserveTime(chineseReserveTime);
        initialOrder.setAvailableNumber(confirmedOrder.getAvailableNumber());
        initialOrder.setReservedNumber(confirmedOrder.getReservedNumber());
        initialOrder.setHosRecordId(confirmedOrder.getHosRecordId());
        initialOrder.setNumber(confirmedOrder.getNumber());
        initialOrder.setFetchTime(confirmedOrder.getFetchTime());
        initialOrder.setFetchAddress(confirmedOrder.getFetchAddress());
        this.orderInfoMapper.confirmInsertion(initialOrder);
        return initialOrder;
    }

    /**
     * 将发送给医院的订单信息转换为 TreeMap 集合
     *
     * @param transmissionInfo 订单信息
     * @return TreeMap 集合
     */
    private TreeMap<String, Object> orderToTreeMap(OrderRetransmissionDto transmissionInfo) {
        try {
            // 创建 ObjectMapper 实例
            ObjectMapper objectMapper = new ObjectMapper();
            // 将对象转换为 JSON 字符串
            String jsonString = objectMapper.writeValueAsString(transmissionInfo);
            // 将 JSON 字符串转换为 TreeMap
            return objectMapper.readValue(jsonString,
                    TypeFactory.defaultInstance().constructMapType(TreeMap.class, String.class, Object.class));
        } catch (JsonProcessingException e) {
            this.logger.error("调用医院接口提交订单失败，Json 格式转换错误", e);
            throw new BusinessException("无法向医院提交订单信息，数据转换失败", ResultCodeEnum.SERVICE_ERROR.getCode(), e);
        }
    }

}
